﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2021-2025 LiLong                                              **
**  This file is part of CommonUnits.                                           **
**                                                                              **
**  CommonUnits is free software: you can redistribute it and/or modify         **
**  it under the terms of the GNU Lesser General Public License as published by **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  CommonUnits is distributed in the hope that it will be useful,              **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU Lesser General Public License for more details.                         **
**                                                                              **
**  You should have received a copy of the GNU Lesser General Public License    **
**  along with CommonUnits.  If not, see <https://www.gnu.org/licenses/>.       **
**********************************************************************************/

#pragma once

#include "../CommonUnits.h"
#include "../UnitLists.h"

#include <chrono>

namespace CU
{
inline namespace Chrono
{
namespace Units
{
using s   = StandardUnit<"s">;
using min = DeriveUnit<s, "min", std::chrono::minutes::period>;
using h   = DeriveUnit<s, "h", std::chrono::hours::period>;
using d   = DeriveUnit<s, "d", std::chrono::days::period>;
using w   = DeriveUnit<s, "w", std::chrono::weeks::period>;
using m   = DeriveUnit<s, "m", std::chrono::months::period>;
using y   = DeriveUnit<s, "y", std::chrono::years::period>;
using ms  = DeriveUnit<s, "ms", std::chrono::milliseconds::period>;
using us  = DeriveUnit<s, "μs", std::chrono::microseconds::period>;
using ns  = DeriveUnit<s, "ns", std::chrono::nanoseconds::period>;
using ps  = DeriveUnit<s, "ps", std::pico>;
using fs  = DeriveUnit<s, "fs", std::femto>;
using as  = DeriveUnit<s, "as", std::atto>;

using ChronoList = UnitLists<as, fs, ps, ns, us, ms, s, min, h, d, w, m, y>;
} // namespace Units

template<typename T>
constexpr bool _isStdDuration = std::false_type {};
template<class _Rep, class _Period>
constexpr bool _isStdDuration<std::chrono::duration<_Rep, _Period>> = std::true_type {};
template<typename T>
concept IsStdDuration = _isStdDuration<T>;

template<typename _Type, UnitType _Unit>
requires std::is_arithmetic_v<_Type>
class Duration : public CommonUnits<_Type, _Unit>
{
public:
    using CommonUnits<_Type, _Unit>::CommonUnits;

    template<IsStdDuration _Dur>
    constexpr Duration(const _Dur& dur)
    {
        operator=(dur);
    }
    template<IsStdDuration _Dur>
    constexpr Duration& operator=(const _Dur& dur)
    {
        using _CF    = std::ratio_divide<typename _Dur::period, typename CommonUnits<_Type, _Unit>::Ratio>;
        using _ToRep = typename CommonUnits<_Type, _Unit>::Type;
        using _CR    = std::common_type_t<_ToRep, typename _Dur::rep, intmax_t>;

        constexpr bool numIsOne = _CF::num == 1;
        constexpr bool denIsOne = _CF::den == 1;

        if constexpr (denIsOne)
        {
            if constexpr (numIsOne)
            {
                this->m_value = static_cast<_ToRep>(dur.count());
            }
            else
            {
                this->m_value = static_cast<_ToRep>(static_cast<_CR>(dur.count()) * static_cast<_CR>(_CF::num));
            }
        }
        else
        {
            if constexpr (numIsOne)
            {
                this->m_value = static_cast<_ToRep>(static_cast<_CR>(dur.count()) / static_cast<_CR>(_CF::den));
            }
            else
            {
                this->m_value =
                    static_cast<_ToRep>(static_cast<_CR>(dur.count()) * static_cast<_CR>(_CF::num) / static_cast<_CR>(_CF::den));
            }
        }
        return *this;
    }

    template<class _Rep, class _Period>
    explicit inline operator std::chrono::duration<_Rep, _Period>() const
    {
        return std::chrono::duration_cast<std::chrono::duration<_Rep, _Period>>(
            std::chrono::duration<_Rep, typename _Unit::Ratio>(static_cast<_Rep>(this->m_value)));
    }
};

template<typename _Unit, typename _UnitLists>
inline auto chronoToUnitHelper(double value, const std::string& dest) -> _Unit
{
    _Unit unit;
    auto ret = visitUnitList<_UnitLists>(
        [&](std::string_view u, std::intmax_t num, std::intmax_t den)
        {
            if (dest == u)
            {
                auto cast = [](double value, std::intmax_t num, std::intmax_t den)
                {
                    auto divideNum = _Unit::Ratio::den * num;
                    auto divideDen = _Unit::Ratio::num * den;
                    using _ToRep   = typename _Unit::Type;
                    using _CR      = std::common_type_t<_ToRep, double, intmax_t>;

                    bool numIsOne = divideNum == 1;
                    bool denIsOne = divideDen == 1;

                    if (denIsOne)
                    {
                        if (numIsOne)
                        {
                            return static_cast<_Unit>(static_cast<_ToRep>(value));
                        }
                        else
                        {
                            return static_cast<_Unit>(static_cast<_ToRep>(static_cast<_CR>(value) * static_cast<_CR>(divideNum)));
                        }
                    }
                    else
                    {
                        if (numIsOne)
                        {
                            return static_cast<_Unit>(static_cast<_ToRep>(static_cast<_CR>(value) / static_cast<_CR>(divideDen)));
                        }
                        else
                        {
                            return static_cast<_Unit>(
                                static_cast<_ToRep>(static_cast<_CR>(value) * static_cast<_CR>(divideNum) / static_cast<_CR>(divideDen)));
                        }
                    }
                };
                unit = cast(value, num, den);
                return true;
            }
            return false;
        });
    if (!ret)
        throw std::invalid_argument("Invalid unit " + dest);
    return unit;
}

using Seconds      = Duration<double, Units::s>;
using Minutes      = Duration<double, Units::min>;
using Hours        = Duration<double, Units::h>;
using Days         = Duration<double, Units::d>;
using Weeks        = Duration<double, Units::w>;
using Months       = Duration<double, Units::m>;
using Years        = Duration<double, Units::y>;
using Milliseconds = Duration<double, Units::ms>;
using Microseconds = Duration<double, Units::us>;
using Nanoseconds  = Duration<double, Units::ns>;
using Picoseconds  = Duration<double, Units::ps>;
using Femtoseconds = Duration<double, Units::fs>;
using Attoseconds  = Duration<double, Units::as>;

namespace Literals
{
constexpr auto operator""_s(long double val) { return Seconds(val); }
constexpr auto operator""_min(long double val) { return Minutes(val); }
constexpr auto operator""_h(long double val) { return Hours(val); }
constexpr auto operator""_ms(long double val) { return Milliseconds(val); }
constexpr auto operator""_us(long double val) { return Microseconds(val); }
constexpr auto operator""_ns(long double val) { return Nanoseconds(val); }
constexpr auto operator""_ps(long double val) { return Picoseconds(val); }
constexpr auto operator""_fs(long double val) { return Femtoseconds(val); }
constexpr auto operator""_as(long double val) { return Attoseconds(val); }
// 不提供 Days，Years的用户字面量，因其与c++标准不同，避免造成误导。
} // namespace Literals

template<typename Unit>
inline auto toUnit(double value, const std::string& dest) -> Unit
{
    return chronoToUnitHelper<Unit, Units::ChronoList>(value, dest);
}
} // namespace Chrono
} // namespace CU
